package cn.edu.dgut.sw.lab.security.oauth2.config;

import cn.edu.dgut.sw.lab.security.oauth2.client.SaiAuthorizationCodeTokenResponseClientProxy;
import cn.edu.dgut.sw.lab.security.oauth2.client.SaiOAuth2AuthorizationRequestResolverProxy;
import cn.edu.dgut.sw.lab.security.oauth2.client.userinfo.SaiOAuth2UserService;
import cn.edu.dgut.sw.lab.security.oauth2.filter.DgutAuthorizationResponseFilter;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.DefaultSecurityFilterChain;

/**
 * @author Sai
 * @since 1.0
 * 2018-8-27
 */
public final class SaiOAuth2LoginSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    private final ClientRegistrationRepository clientRegistrationRepository;
    private final SaiOAuth2ConfigProperties saiOAuth2ConfigProperties;

    private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = new SaiAuthorizationCodeTokenResponseClientProxy();
    private final OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = new SaiOAuth2UserService();

    public SaiOAuth2LoginSecurityConfigurer(ClientRegistrationRepository clientRegistrationRepository, SaiOAuth2ConfigProperties saiOAuth2ConfigProperties) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.saiOAuth2ConfigProperties = saiOAuth2ConfigProperties;
    }

    /**
     * 配置oauth2login()
     *
     * 注意：这里"必须"写在init()里，不能写在configure()
     * 主要是因为oauth2login()使用了{@link HttpSecurity#getOrApply(SecurityConfigurerAdapter)}
     * 创建一个{@link org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer}的实例，并add进列表，
     * 但在{@link HttpSecurity#doBuild()}的configure()阶段，执行{@link HttpSecurity#add(SecurityConfigurer)}时会抛异常，
     * 这是因为{@link HttpSecurity#add(SecurityConfigurer)}方法里会判断{@link HttpSecurity#buildState}，
     * 在{@link HttpSecurity#doBuild()}的configure()阶段，buildState.isConfigured()返回true，
     * 从而导致抛异常IllegalStateException("Cannot apply " + configurer + " to already built object")；
     *
     * 所以，配置oauth2login()的代码只能写在builder的init()阶段，否则抛异常。
     *
     * @param http HttpSecurity builder
     * @see HttpSecurity#doBuild()
     * @see HttpSecurity#init()
     * @see HttpSecurity#configure()
     * @see HttpSecurity#buildState
     * @see HttpSecurity#getOrApply(SecurityConfigurerAdapter)
     * @see HttpSecurity#add(SecurityConfigurer)
     */
    @Override
    public void init(HttpSecurity http) throws Exception {

        /*
          添加一个filter,过滤dgut登录时的token参数改名为code
         */
        http.addFilterBefore(postProcess(createDgutAuthorizationResponseFilter()), OAuth2LoginAuthenticationFilter.class);

        /*
          配置{@link org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer}
         */
        http.oauth2Login()
                .authorizationEndpoint()
                .baseUri(saiOAuth2ConfigProperties.getAuthorizationRequestBaseUri())
                .authorizationRequestResolver(createOAuth2AuthorizationRequestResolver())
                .and()
                .tokenEndpoint()
                .accessTokenResponseClient(accessTokenResponseClient)
                .and()
                .redirectionEndpoint()
                .baseUri(saiOAuth2ConfigProperties.getAuthorizationResponseBaseUri())
                .and()
                .userInfoEndpoint()
                .userService(oauth2UserService);
    }

    private OAuth2AuthorizationRequestResolver createOAuth2AuthorizationRequestResolver() {
        return new SaiOAuth2AuthorizationRequestResolverProxy(clientRegistrationRepository, saiOAuth2ConfigProperties.getAuthorizationRequestBaseUri());
    }

    private DgutAuthorizationResponseFilter createDgutAuthorizationResponseFilter() {
        return new DgutAuthorizationResponseFilter();
    }

}
